Skip to content

wallet: Close incomplete descriptor rescans - #113

Open
BenWestgate wants to merge 2 commits into
restore-date-reviewfrom
gui-before-you-start
Open

BenWestgate wants to merge 2 commits into
restore-date-reviewfrom
gui-before-you-start

Conversation

@BenWestgate

@BenWestgate BenWestgate commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Why

A pruning preflight is not conclusive: Bitcoin Core chooses the rescan start using the highest prior block timestamp, but the API exposes only individual block times. Non-monotonic timestamps can let the preflight pass even when the actual descriptor rescan later reports Rescan failed. In that case Core has already imported keys into a wallet missing transaction history.

Change

  • 6afb94f: on Rescan failed, try unloadwallet <name> false (also remove from startup); retain the original diagnosis if unload fails or warns, and instruct the operator to close and replace the incomplete wallet. Preserve encrypted-wallet relock warnings.
  • 6c64411: Ctrl-C at the create --existing first-use date prompt must not instruct users to discard valid recovery cards.

Review

This is now a two-commit PR stacked on #191 (which stacks on #190). The original four commits are unchanged; the large preparation and dated-rescan changes have dedicated PRs. The earlier merged-to-branch #139 review covers the date work. Relevant unit tests and exact-head CI previously passed, but supported-Tails/pruned-Core integration testing is still required. Prepared with AI assistance; final human review required.

@BenWestgate BenWestgate self-assigned this Oct 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7181c7b70e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex32_gui/pages.py Outdated
Comment thread src/codex32_gui/pages.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d53a4a426

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex32_gui/pages.py
@BenWestgate BenWestgate changed the title gui: Ask for cards and a record before creating gui: Ask for cards, a record and marked look-alikes Oct 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1863df760

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/test_gui_boundaries.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d65adc189

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/test_gui_before_you_start.py Outdated

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-generated review (Codex), posted at the maintainer's request.

Not ACKing 175cf4c yet: the GUI budget is documented consistently at 2,050 now, but its required explicit maintainer authorization is still missing.

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex current-head review at 175cf4c: one existing P2 remains. The GUI budget is now documented as 2,050 lines, but the repository contract requires explicit maintainer authorization for a budget increase and none is recorded.

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-assisted current-head review of 175cf4c: one blocking documentation/security-contract issue. The new Before you start text says the separate wallet record “proves later that cards you restore are this wallet.” That overstates the settled threat model: the 32-bit master fingerprint is a human-scale accident-safety check for wrong/mixed cards and transcription/correction mistakes; it is not malicious-tampering authentication. Please change this to wording such as “helps confirm later that the restored cards match your recorded wallet before import,” while keeping the stronger authenticated descriptor-backup work separate. I found no other blocker in the bundled-form launcher, package-data boundary, handwriting key, or pre-seed checklist flow.

@BenWestgate
BenWestgate force-pushed the gui-before-you-start branch from 207c0f4 to e43102f Compare October 7, 2026 04:33
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T12:28:56.922487Z 6c64411 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

This comment has been minimized.

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI current-head review: ACK bd49e52 for this branch's scope. The checklist occurs before secret generation, only bundled blank forms are handed to the desktop, GTK <4.10 takes the explicit path fallback, package data includes both forms, and CODEX32_FORMS_DIR cleanly supplies a confinement-readable copy. The remaining Tails integration dependency is already explicit: Bails must copy the forms into a Tor-Browser-readable directory and set that variable when it repins. No additional code finding here.

@BenWestgate

Copy link
Copy Markdown
Owner Author

cNACK put a dot in each 6, there's no evidence this is a standard way to disambiguate these letters, we can simply say to make G's horizontal bar obvious and open, while to close the loops of 6 and 9

Copy link
Copy Markdown
Owner Author

Done in d08bbbe. The dot is gone. The window, the printable recovery card and docs/user/gui.md now say to close the loops of 6 and 9 and give G an open, obvious bar. The GUI stays at 2,049 lines, and the GUI tests pass. I updated the PR description to match. It also now notes that this branch carries #139.


Generated by Claude Code

@BenWestgate

Copy link
Copy Markdown
Owner Author

please read CONTRIBUTING.md and squash this into atomic easy to review commits before human review.

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-assisted current-head review: Concept ACK d08bbbe. The form preflight precedes seed generation; the corrected handwriting key is consistent. The #139 changes gate dated restore on Core's prune/AssumeUTXO state before import and keep new wallets loaded on startup, with focused tests. Not ready for human review until the requested atomic commit cleanup; also test the complete restore and form-opening flow on supported Tails.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aea9da63e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex32_gui/wallet_setup.py Outdated
Comment on lines +242 to +243
if core._target(name) is None or any(type(w) is not str or "could not be updated" in w for w in notes):
raise BitcoinCoreError("Bitcoin Core did not create an empty wallet that it loads at every start.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Quarantine the wallet when autoload persistence fails

When createwallet succeeds but reports that load_on_startup could not be saved, this raises only after Core has already created and loaded the blank wallet. Core's createwallet contract returns the created wallet name together with any warnings, so the wallet remains eligible to _wallets; after choosing Start again or Restore my wallet, the operator can select and fill it even though it will not load after a restart. On a pruned node that wallet can then fall behind the retained blocks, defeating the protection this option was added to provide. Keep this wallet out of subsequent selection, or otherwise ensure it cannot be filled until startup persistence is confirmed.

AGENTS.md reference: AGENTS.md:L78-L82

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

report Core's warning is sufficient.

Comment thread src/codex32_gui/pages.py
if mismatch:
return _fingerprint_page(view, core, secret, timestamp, restoring=restoring, problem=mismatch)
_wallets(view, core, secret, timestamp, expected, restoring=restoring)
_wallets(view, core, secret, start, expected, restoring=restoring)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the dated restore instead of a genesis scan

When restoration uses a nonblank creation date, this line passes start—one day before that date—to importdescriptors; Core documents that the rescan is based on the earliest supplied timestamp. The resulting _RESTORED screen nevertheless tells the operator that Core is scanning “from the beginning.” A late or mistyped approximate date can therefore omit older transactions while the completion screen falsely implies that all chain history was checked; describe the selected dated scan instead, or retain timestamp zero if promising a genesis scan.

AGENTS.md reference: AGENTS.md:L78-L82

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

describe the selected dated scan instead

Comment thread src/codex32/_bitcoin_core.py Outdated
Comment on lines +264 to +266
kept = t if isinstance(b, dict) and type(t := b.get("time")) is int and t >= genesis else 0
if start < kept + 86400 or not kept:
since = f" from before {time.strftime('%Y-%m-%d', time.gmtime(kept))}" if kept else ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the retained block time before formatting it

If the untrusted getblockstats response contains a very large integer such as 10**20 for time, it passes the current lower-bound check and reaches time.gmtime(kept), which raises OverflowError on supported platforms instead of the promised BitcoinCoreError. The CLI does not catch that exception and can terminate with a traceback during wallet initialization, while the GUI reports only its generic unexpected-failure message. Apply a sane upper bound to the retained-block timestamp, or translate failures from the date conversion into BitcoinCoreError.

AGENTS.md reference: AGENTS.md:L78-L80

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply a sane upper bound to the retained-block timestamp

@BenWestgate BenWestgate changed the title gui: Ask for cards, a record and marked look-alikes gui: Prepare records and restore from their date Oct 10, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b045908d2a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +261 to +264
kept = b.get("time") if isinstance(b, dict) else None
if type(kept) is not int or not genesis <= kept < time.time() + 7200:
raise BitcoinCoreError("Bitcoin Core did not say which blocks it still keeps.")
if start < kept + 86400:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use Core's cumulative block time for the pruning gate

When the first retained block's raw timestamp lags a pruned predecessor's timestamp by more than roughly one day—a reproducible regtest case after a mock-time gap and valid under consensus rules—this comparison can approve a date even though Core will start the rescan in pruned history. RescanFromTime selects the first block using cumulative GetBlockTimeMax, not the prune-height block's raw time; importdescriptors can therefore fail after modifying the destination wallet, defeating this preflight. Base the gate on the same cumulative maximum or verify that Core's actual starting block is retained.

AGENTS.md reference: AGENTS.md:L78-L80

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base the gate on the same cumulative maximum

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core doesn't expose the cumulative maximum over RPC. GetBlockTimeMax is only reachable through FindEarliestAtLeast, and the only RPC that calls that is pruneblockchain, which deletes blocks. getblockheader and getblockstats return only raw time and mediantime. Raw times aren't monotonic, so computing the maximum ourselves means reading every header below the prune height. That's one bitcoin-cli call per block, hundreds of thousands on a pruned mainnet node.

The exact signal we do get is Core's own per-descriptor "Rescan failed … pruning" error from importdescriptors. Today that surfaces as the generic "did not import every private descriptor". I'm proposing to keep the cheap preflight as a first filter, and to turn that error into a clear "this pruned node lacks blocks from your date" failure that unloads the wallet the restore just created. The alternatives are to keep the one-day margin as is, or to walk all the headers. I'm asking which you want in the project thread, so this reply doesn't stand in for that choice.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 59d7b3c, following the option you picked. The cheap preflight stays. When importdescriptors reports "Rescan failed", initialize now runs unloadwallet <name> false, so the wallet isn't loaded on startup, and raises a message saying the wallet is missing history and was closed. That replaces the generic import failure. A test covers it. The library is at 5,019 lines, inside the 5,025 budget. This is one commit on top, ready to squash into "wallet: Rescan from recorded creation date".


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59d7b3cb6e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"""Create one blank descriptor wallet and return Core's warnings for the operator."""
# Let Core persist startup loading so the wallet follows new blocks.
arguments = [f"wallet_name={_wallet_name(name)}", "disable_private_keys=false", "blank=true"]
arguments.append("load_on_startup=true")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist selected wallets before permitting pruned restores

When restoring on a pruned node into an eligible wallet that was loaded transiently (for example, with loadwallet name false), this persistence flag never runs: _wallet_page sends existing choices directly to _import, so the dated restore can succeed but the wallet disappears after the next Core restart and can fall behind blocks that are subsequently pruned. Bitcoin Core defines false as removing the wallet from its persistent startup list in the loadwallet contract. Ensure selected existing destinations are persistent, or reject/warn for pruned restores, rather than protecting only wallets created here. This differs from the already-discussed createwallet-warning case because this path makes no createwallet call and produces no persistence warning.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't something this PR introduced: the existing-wallet path has never set load_on_startup. Core has no RPC that changes that flag on a wallet that's already loaded. The only way is unloadwallet followed by loadwallet name true, which would close a wallet the operator opened themselves. Bails also already reloads every wallet with load_on_startup on each Core start (Bails #315, load-wallets). I'm leaving this for Ben to decide rather than widening this PR.


Generated by Claude Code

Comment thread src/codex32/_bitcoin_core.py Outdated
Comment thread src/codex32/_bitcoin_core.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b63b112ba9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex32_gui/wallet_setup.py Outdated
Comment thread tests/test_cli.py
Comment thread src/codex32/_bitcoin_core.py
Comment thread src/codex32/cli.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aaff90fb33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex32/_bitcoin_core.py Outdated
Core starts a dated rescan at the first block whose highest time so
far reaches the date. No RPC reports that cumulative time, so the
pruning check reads the oldest kept block's own time, and a pruned
block that ran more than a day ahead can still pass it. Core then
imports the keys, reports "Rescan failed" for each descriptor, and
leaves a wallet that is missing history.

When that happens, ask Core to unload the wallet and drop it from
startup, and say why instead of the generic import failure. The
message tells the operator not to use the wallet and to close it
whenever it is open, since the unload can fail or be undone at the
next start. It advises a later date only when Core blames pruning;
an aborted scan needs the same date again. The GUI's relock treats a
wallet that a successful listing no longer shows as locked, so it
does not hide that message, and still asks for a check if Core stops
answering.

Shorten the blank wallet prompts and the createwallet check to stay
inside the library and GUI line budgets.

Refs BenWestgate/Bails#314
Pressing Ctrl-C at the first-use date prompt of create --existing
reached the generic create handler, which tells the operator to void
every card from the incomplete creation. Nothing has been made at
that point, and the only cards in play are the operator's existing,
valid backup.

Report a plain interruption there instead.

Refs BenWestgate/Bails#314

@BenWestgate BenWestgate left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-assisted current-head review.

Code ACK 6c64411 (static). The new rescan-failure path preserves the warning if unload fails and relocks the wallet; interrupting an existing-seed date prompt no longer voids valid cards. Targeted regression tests are present and CI passes. Before human review, update the PR body: it says two commits, but this head has four. Supported-Tails GUI/restore testing remains outstanding.

@BenWestgate

Copy link
Copy Markdown
Owner Author

cACK commit 1,2, and 4
I need to see a clearer explanation of why 3 is needed

aNACK I'm not reviewing +856 lines

CONTRIBUTING.md says:

Please also avoid super pull requests which attempt to do too much, are overly large, or overly complex as this makes review difficult.

Consider breaking these apart into smaller focused PRs, do less, shrink the diffs, and/or simplify your changes to make an easy review practical.

@BenWestgate BenWestgate changed the title gui: Prepare records and restore from their date wallet: Close incomplete descriptor rescans Oct 24, 2026
@BenWestgate
BenWestgate changed the base branch from bails-v1-pin to restore-date-review October 24, 2026 05:23

Copy link
Copy Markdown
Owner Author

AI-assisted response to review feedback: agreed. Split into #190 (forms), #191 (dated restore), and this PR (#113), now just 2 commits / +121 −30 lines; no force-push.

Commit 3 is needed because non-monotonic block timestamps can defeat the pruning preflight. Core then reports Rescan failed after importing keys, leaving a wallet without complete history. The patch attempts Core's unloadwallet … false, preserves failure/lock warnings, and tells users to replace that wallet. The small Ctrl-C fix is commit 4.

Existing tests and CI cover the code; supported-Tails/pruned-Core end-to-end testing remains open.

@BenWestgate
BenWestgate added this pull request to stack #204 October 24, 2026 13:39
@BenWestgate

Copy link
Copy Markdown
Owner Author

+121 is a lot of code for what seems like a simple bounded problem of how "off" the time reported by the RPC can be. Are you sure there is not a MUCH simpler solution like being conservative about which blocks we have so that this doesn't need so much edge case handling?

Copy link
Copy Markdown
Owner Author

AI-assisted response to the size concern: no fixed timestamp margin guarantees a safe rescan on pruned Core. Core's selection depends on the maximum preceding block time, which the preflight RPCs do not expose; failed rescans can also have other causes. A larger buffer could reduce failures but cannot replace handling Rescan failed after keys are imported. The actual fix is a short unload/quarantine path plus a separate Ctrl-C correction; most of the +121 lines are regression tests and documentation. I recommend keeping this fail-closed cleanup.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants